package com.feedgraph;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.json.JSONArray;

import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.net.http.AndroidHttpClient;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import com.feedgraph.PlacesManager.Place;

public class PlacesService extends Service implements LocationListener {
	
	private static final String TAG = "PlacesService";

	public static final String PLACE_API_URI = "http://places.feedgraph.com/nearby";
	public static final String LOCATION_UPDATE_INTENT = "com.feedgraph.PlacesService.LOCATION_UPDATE_INTENT";
	
	private LocationManager mLocationManager;
	private String mProvider;
	private Map <PlaceListener, PlaceListenerInfo> mListeners = new HashMap <PlaceListener, PlaceListenerInfo> ();
	 // This is the object that receives interactions from clients.  See
  // RemoteService for a more complete example.
  private final IBinder mBinder = new LocalBinder();
	static private Place mCurrentPlace;

	
	 /**
   * Class for clients to access.  Because we know this service always
   * runs in the same process as its clients, we don't need to deal with
   * IPC.
   */
  public class LocalBinder extends Binder {
      public PlacesService getService() {
          return PlacesService.this;
      }
  }

  @Override
  public void onCreate() {
    Log.i(getClass().getSimpleName (), "onCreate");

  	mLocationManager = (LocationManager) getSystemService (Context.LOCATION_SERVICE);
		mProvider = mLocationManager.getBestProvider (new Criteria (), true);
	}

  /**
   * Start monitoring location and place updates.
   */
  public void requestLocationUpdates(int minTime, int minDist) {
      Log.i(getClass().getSimpleName (), "requestLocationUpdates");
      
  		mLocationManager.requestLocationUpdates (mProvider, minTime, minDist, this);
  }
  
  @Override
  public int onStartCommand(Intent intent, int flags, int startID) {
  	Log.i(getClass().getSimpleName (), "onStartCommand");
  	
  	mLocationManager.requestSingleUpdate (mProvider, this, null);
  	
    // We want this service to continue running until it is explicitly
    // stopped, so return sticky.
    return START_STICKY;
  }
  
  public void requestSingleUpdate() {
  	mLocationManager.requestSingleUpdate (mProvider, this, null);
  }
    
  public static Intent getIntent(Context context) {
  	return new Intent(context, PlacesService.class);
  }

  @Override
  public void onDestroy() {
  	Log.i(getClass().getSimpleName (), "onDestroy");
  	
  	mLocationManager.removeUpdates (this);
  }

  @Override
  public IBinder onBind(Intent intent) {
  	Log.i(getClass().getSimpleName (), "onBind");
    return mBinder;
  }

  public void addListener (PlaceListener listener, int searchRadius, String []searchTypes) {
  	Log.i(PlacesService.class.getSimpleName (), "listener added");
  	PlaceListenerInfo info = new PlaceListenerInfo();
  	info.mListener = listener;
  	info.mSearchRadius = searchRadius;
  	info.mSearchTypes = TextUtils.join ("|", searchTypes);
  	mListeners.put (listener, info);
  }
  
  public void removeListener(PlaceListener listener) {
  	mListeners.remove (listener);
  }
  
  static public Place getCurrentPlace() {
  	return mCurrentPlace;
  }
  
	private void storePlace (Place p) {
		getContentResolver().insert (PlacesManager.CONTENT_URI, p.toContentValues());		
	}
	
	public interface PlaceListener {

		/**
		 * indicates that a place is within a large radius from the device's
		 * location
		 * 
		 * @param place
		 */
		void onNearbyPlace (Place place);

		/**
		 * indicates that the device has moved within a tight radius of a place
		 * 
		 * @param place
		 */
		void onEnteringPlace (Place place);

		/**
		 * indicates that the device has moved away from a place
		 * 
		 * @param place
		 */
		void onLeavingPlace (Place place);
	}
	
	private static class PlaceListenerInfo {
		protected PlaceListener mListener;
		protected int mSearchRadius;
		protected String mSearchTypes;
		protected Place mCurrentPlace;
		protected Set <Place> mPlaces = new TreeSet <Place> ();	
	}		
	

	@Override
	public void onLocationChanged (Location location) {
		
		Log.d (getClass().getSimpleName (), "received location update intent: " + location.toString ());

		for (PlaceListenerInfo info : mListeners.values ()) {
			// search for nearby places once for each listener
			fetchPlaces(info, location);
		}
	}

	@Override
	public void onProviderDisabled (String provider) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void onProviderEnabled (String provider) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void onStatusChanged (String provider, int status, Bundle extras) {
		// TODO Auto-generated method stub
		
	}
	
	protected void fetchPlaces(final PlaceListenerInfo info, final Location location) {
		Log.d (getClass().getSimpleName (), "GETing new place info");
		
		final List <Place> newPlaces = new LinkedList <Place> ();

		AsyncTask <Void, Void, Void> task = new AsyncTask <Void, Void, Void> () {

			@Override
			protected void onPostExecute (Void params) {

				Place closestPlace = null;
				float closestDistance = 500; // meters -- a pretty big place!

				for (Place p : newPlaces) {
					storePlace(p);
					info.mListener.onNearbyPlace (p);

					float distance = p.getLocation (mProvider).distanceTo (location);

					if (distance < p.mRadius && distance < closestDistance) {
						closestDistance = distance;
						closestPlace = p;
						// already at the place
					}
				}

				if (closestPlace != null) {
					mCurrentPlace = closestPlace;
					if (info.mCurrentPlace != null && !info.mCurrentPlace.equals (closestPlace)) {
						info.mListener.onLeavingPlace (info.mCurrentPlace);
					}
					info.mCurrentPlace = closestPlace;
					info.mListener.onEnteringPlace (closestPlace);
				}
			}

			@Override
			protected Void doInBackground (Void... params) {
				AndroidHttpClient client = AndroidHttpClient.newInstance ("");
				HttpGet req = new HttpGet (PLACE_API_URI + "?lat="
						+ location.getLatitude () + "&long=" + location.getLongitude ()
						+ "&radius=" + info.mSearchRadius + "&types=" + Uri.encode (info.mSearchTypes));
				
				Log.d(TAG, req.getURI ().toString ());
				
				try {
					HttpResponse resp = client.execute (req);
					InputStream stream = resp.getEntity ().getContent ();
					String str = IOUtil.toString (stream);
					
					if (str.compareTo ("null") == 0) {
						Log.d (TAG, "nothing nearby");
						return null;
					}

					JSONArray json = new JSONArray (str);

					for (int i = 0; i < json.length (); i++) {
						Place place = new Place (json.getJSONObject (i));
						if (!info.mPlaces.contains (place)) {
							info.mPlaces.add (place);
							newPlaces.add (place);
						}
					}
				}
				catch (Exception e) {
					Log.d (TAG, "nearby", e);
				}
				finally {
					client.close ();
				}
				return null;
			}
		};

		task.execute ();
	}
}
